/*
 * Copyright Radosław Smogura, 2012. All rights reserved.
 *
 * The content of this file is confidential and it is intelectual property
 * of copyright owners.
 * 
 *
 * $URL: http://reprap-ide.googlecode.com/svn/trunk/RepRapIDE/RepRapProject/src/main/java/eu/softper/reprap/project/filetypes/LayerPreview.java $
 * $Id: LayerPreview.java 3 2012-11-19 10:58:30Z mail@smogura.eu $
 * $Revision: 3 $
 */
package eu.softper.reprap.project.filetypes;

import eu.softper.reprap.generator.SceneTriangle;
import eu.softper.reprap.generator.GenerationContextImpl;
import eu.softper.reprap.generator.SceneLine;
import eu.softper.reprap.generator.DecimalVertex;
import eu.softper.reprap.generator.SceneHandler;
import eu.softper.reprap.generator.MultiThreadSlicerImpl;
import eu.smogura.stlparser.STLParserFactory;
import eu.softper.reprap.generator.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.math.BigDecimal;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.MultiViewElementCallback;
import org.openide.awt.UndoRedo;
import org.openide.filesystems.FileObject;
import org.openide.util.Lookup;
import org.openide.windows.TopComponent;

@MultiViewElement.Registration(displayName = "#LBL_StlMime_LayerPreview",
iconBase = "eu/softper/reprap/project/images/drawing.png",
mimeType = "application/sla",
persistenceType = TopComponent.PERSISTENCE_ALWAYS,
preferredID = "StlLayerPreview",
position = 3000)
public final class LayerPreview extends JPanel implements MultiViewElement {

    private StlMimeDataObject obj;
    private transient MultiViewElementCallback callback;
    private ArrayList<SceneTriangle<BigDecimal>> trainglesOrdered; 
    private List<Layer> layers;
    private BufferedImage image;
    private DrawingPanel imagePanel;
    private Integer lastLayer = -1;
    private boolean antialias = true;
    
    private GenerationContextImpl ctx = new GenerationContextImpl();
    private GeneratorFactory<?> generator;
    private double scale;
    
    protected double calculateScale() {
        /* We assume that 1 is one centimeter, so 0.1 is 1 mm - ugly... */
        double scale = Toolkit.getDefaultToolkit().getScreenResolution();
        scale = scale / 25d; // Points per centimeter on monitor:)
        scale = scale * (((double) jZoomSlider.getValue()) / 100.0d); //Points pcm after zoom
        
        return scale;
    }
    
    private void drawSceneLine(SceneLine line, Graphics2D g, double scale) {
        DecimalVertex v1 = line.getV1();
        DecimalVertex v2 = line.getV2();
            
        g.drawLine((int) ((v1.getX().doubleValue() - generator.getHandler().getMinX().doubleValue()) * scale), 
                    (int) ((v1.getY().doubleValue() - generator.getHandler().getMinY().doubleValue()) * scale),
                    (int) ((v2.getX().doubleValue() - generator.getHandler().getMinX().doubleValue()) * scale), 
                    (int) ((v2.getY().doubleValue() - generator.getHandler().getMinY().doubleValue())* scale));
    }
    protected void updateLayer() {
        scale = calculateScale();
        int imgWidth = (int) ((generator.getHandler().getMaxX().doubleValue() - generator.getHandler().getMinX().doubleValue()) * scale) + 5;
        int imgHeight = (int) ((generator.getHandler().getMaxY().doubleValue() - generator.getHandler().getMinY().doubleValue()) * scale) + 5;
        
        /* Create new image only if required. */
        if (image == null || image.getWidth() != imgWidth || image.getHeight() != imgHeight)  {
            image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
        }
        
        Graphics2D g = (Graphics2D) image.getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, image.getWidth(), image.getHeight());
        imagePanel.repaint();
        jScrollPane1.invalidate();
        final Layer<Double> layer = layers.get(((Number) layerSpinner.getValue()).intValue() - 1);
        Set<SceneLine<Double>> lines = layer.getOutlines();
        g.setColor(Color.BLACK);
        for (SceneLine line : lines) {
            switch (line.getLineType()) {
                case INNER:
                    g.setColor(Color.YELLOW);
                    continue;
//                    break;
                case OUTLINE:
                    g.setColor(Color.BLACK);
                    break;
                default:
                    g.setColor(Color.RED);
            }
            drawSceneLine(line, g, scale);
        }
        
        if (jInfillShow.isSelected()) {
            g.setColor(Color.BLUE);
            for (SceneLine<Double> l : layer.getInfills()) {
                drawSceneLine(l, g, scale);
            }
        }
        
        if (jOutline.isSelected()) {
            g.setColor(Color.RED);
            for (List<SceneLine<Double>> outline : layer.getReducedOutlines()) {
                for (SceneLine l : outline) {
                    drawSceneLine(l, g, scale);
                }
            }
        }
    }
    
    protected void sliceLayers() {
        MultiThreadSlicerImpl<?> slicer = generator.createSlicer();
        slicer.run();
        this.layers = slicer.getOutlines();
        
        ((SpinnerNumberModel) this.layerSpinner.getModel()).setMaximum(this.layers.size());
        this.layerSpinner.setValue(Integer.valueOf(1));
        this.layerSlider.setMaximum(this.layers.size());
        this.layerSlider.setValue(1);
        this.layersNumber.setText(Integer.toString(this.layers.size()));
        
        updateLayer();
    }
    
    public LayerPreview(Lookup lkp) throws Exception {
        obj = lkp.lookup(StlMimeDataObject.class);
        assert obj != null;
        initComponents();
        jScrollPane1.setViewportView(imagePanel = new DrawingPanel());
        ((SpinnerNumberModel) this.layerSpinner.getModel()).addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                if (layerSlider.getValue() != ((Number) layerSpinner.getValue()).intValue()) {
                    layerSlider.setValue(((Number) layerSpinner.getValue()).intValue());
                }
                updateLayer();
            }
        });
        
        imagePanel.addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                super.mouseMoved(e);
                double x = ((double) e.getX()) / scale + generator.getHandler().getMinX().doubleValue();
                double y = ((double) e.getY()) / scale + generator.getHandler().getMinY().doubleValue();
                
                jPosition.setText("X: " + x + " Y: " + y);
            }            
        });
        FileObject fo = obj.getPrimaryFile();
        
        ctx.setStepX(new BigDecimal("0.01"));
        ctx.setStepY(new BigDecimal("0.01"));
        ctx.setStepZ(new BigDecimal("0.5"));
        ctx.setStepZ(BigDecimal.ZERO);
        
        //Leave standard values 90, 0 to see some artifacts.
        ctx.setEvenInfill(BigDecimal.valueOf(0));
        ctx.setOddInfill(BigDecimal.valueOf(0));
        
        generator = GeneratorFactory.createFactory(
            //new NumericFactoryBigDecimal(), 
            new NumericFactoryDouble(),
            1, ctx);
        STLParserFactory.getInstance().createParser().parse(fo.getInputStream(), generator.getHandler());
        sliceLayers();
    }

    @Override
    public String getName() {
        return "Layers preview";
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        toolbar = new javax.swing.JToolBar();
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        jZoomSlider = new javax.swing.JSlider();
        jLabel4 = new javax.swing.JLabel();
        layerSpinner = new javax.swing.JSpinner();
        jLabel1 = new javax.swing.JLabel();
        jInfillShow = new javax.swing.JCheckBox();
        jOutline = new javax.swing.JCheckBox();
        layersNumber = new javax.swing.JLabel();
        drawingPanel = new javax.swing.JPanel();
        jPanel1 = new javax.swing.JPanel();
        layerSlider = new javax.swing.JSlider();
        jPanel2 = new javax.swing.JPanel();
        jPosition = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();

        setLayout(new java.awt.BorderLayout());

        toolbar.setFloatable(false);
        toolbar.setRollover(true);

        org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(LayerPreview.class, "LayerPreview.jLabel2.text")); // NOI18N
        toolbar.add(jLabel2);
        toolbar.add(jLabel3);

        jZoomSlider.setMaximum(500);
        jZoomSlider.setMinorTickSpacing(25);
        jZoomSlider.setPaintTicks(true);
        jZoomSlider.setValue(100);
        jZoomSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                jZoomSliderStateChanged(evt);
            }
        });
        toolbar.add(jZoomSlider);

        org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(LayerPreview.class, "LayerPreview.jLabel4.text")); // NOI18N
        toolbar.add(jLabel4);

        layerSpinner.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(1), Integer.valueOf(1), null, Integer.valueOf(1)));
        layerSpinner.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
            public void propertyChange(java.beans.PropertyChangeEvent evt) {
                layerSpinnerPropertyChange(evt);
            }
        });
        toolbar.add(layerSpinner);

        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LayerPreview.class, "LayerPreview.jLabel1.text")); // NOI18N
        toolbar.add(jLabel1);

        org.openide.awt.Mnemonics.setLocalizedText(jInfillShow, org.openide.util.NbBundle.getMessage(LayerPreview.class, "LayerPreview.jInfillShow.text")); // NOI18N
        jInfillShow.setFocusable(false);
        jInfillShow.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jInfillShow.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jInfillShow.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jInfillShowActionPerformed(evt);
            }
        });
        toolbar.add(jInfillShow);

        org.openide.awt.Mnemonics.setLocalizedText(jOutline, org.openide.util.NbBundle.getMessage(LayerPreview.class, "LayerPreview.jOutline.text")); // NOI18N
        jOutline.setFocusable(false);
        jOutline.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        jOutline.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        jOutline.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jOutlineActionPerformed(evt);
            }
        });
        toolbar.add(jOutline);
        toolbar.add(layersNumber);

        add(toolbar, java.awt.BorderLayout.PAGE_START);

        drawingPanel.setLayout(new java.awt.BorderLayout());

        jPanel1.setLayout(new java.awt.GridLayout(1, 0));

        layerSlider.setMaximum(1);
        layerSlider.setMinimum(1);
        layerSlider.setOrientation(javax.swing.JSlider.VERTICAL);
        layerSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                layerSliderStateChanged(evt);
            }
        });
        jPanel1.add(layerSlider);

        drawingPanel.add(jPanel1, java.awt.BorderLayout.LINE_END);

        jPanel2.setLayout(new java.awt.GridLayout(1, 0));

        org.openide.awt.Mnemonics.setLocalizedText(jPosition, org.openide.util.NbBundle.getMessage(LayerPreview.class, "LayerPreview.jPosition.text")); // NOI18N
        jPanel2.add(jPosition);

        drawingPanel.add(jPanel2, java.awt.BorderLayout.PAGE_END);
        drawingPanel.add(jScrollPane1, java.awt.BorderLayout.CENTER);

        add(drawingPanel, java.awt.BorderLayout.CENTER);
    }// </editor-fold>//GEN-END:initComponents

    private void layerSpinnerPropertyChange(java.beans.PropertyChangeEvent evt) {//GEN-FIRST:event_layerSpinnerPropertyChange
        if ("value".equals(evt.getPropertyName())) {
            updateLayer();
        }
    }//GEN-LAST:event_layerSpinnerPropertyChange

    private void layerSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_layerSliderStateChanged
        this.layerSpinner.setValue(this.layerSlider.getValue());
    }//GEN-LAST:event_layerSliderStateChanged

    private void jZoomSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jZoomSliderStateChanged
        updateLayer();
        this.jLabel3.setText("" + jZoomSlider.getValue() + "%");
    }//GEN-LAST:event_jZoomSliderStateChanged

    private void jInfillShowActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jInfillShowActionPerformed
        updateLayer();
    }//GEN-LAST:event_jInfillShowActionPerformed

    private void jOutlineActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jOutlineActionPerformed
        updateLayer();
    }//GEN-LAST:event_jOutlineActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel drawingPanel;
    private javax.swing.JCheckBox jInfillShow;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JCheckBox jOutline;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JLabel jPosition;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JSlider jZoomSlider;
    private javax.swing.JSlider layerSlider;
    private javax.swing.JSpinner layerSpinner;
    private javax.swing.JLabel layersNumber;
    private javax.swing.JToolBar toolbar;
    // End of variables declaration//GEN-END:variables
    @Override
    public JComponent getVisualRepresentation() {
        return this;
    }

    @Override
    public JComponent getToolbarRepresentation() {
        return toolbar;
    }

    @Override
    public Action[] getActions() {
        return new Action[0];
    }

    @Override
    public Lookup getLookup() {
        return obj.getLookup();
    }

    @Override
    public void componentOpened() {
    }

    @Override
    public void componentClosed() {
        this.callback = null;
        layers = null;
        generator = null;
        ctx = null;
        this.trainglesOrdered = null;
        drawingPanel.removeAll();
        image = null;
        imagePanel = null;
        obj = null;
    }

    @Override
    public void componentShowing() {
    }

    @Override
    public void componentHidden() {
    }

    @Override
    public void componentActivated() {
    }

    @Override
    public void componentDeactivated() {
    }

    @Override
    public UndoRedo getUndoRedo() {
        return UndoRedo.NONE;
    }

    @Override
    public void setMultiViewCallback(MultiViewElementCallback callback) {
        this.callback = callback;
    }

    @Override
    public CloseOperationState canCloseElement() {
        return CloseOperationState.STATE_OK;
    }
    
    private class DrawingPanel extends JPanel {
        @Override
        protected void paintComponent(Graphics g) {
            if (image == null)
                return;
            g.drawImage(image, 0, 0, DrawingPanel.this);
        }
        
        @Override
        public Dimension getPreferredSize() {
            if (image == null)
                return new Dimension();
            else
                return new Dimension(image.getWidth(), image.getHeight());
        }
    }
}
